package com.bugsnag.ohos;

import com.bugsnag.ohos.internal.ImmutableConfig;
import com.bugsnag.ohos.util.FileUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.nio.charset.Charset;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * Persists/loads [LastRunInfo] on disk, which allows Bugsnag to determine
 * whether the previous application launch crashed or not. This class is thread-safe.
 *
 * @since 2021-07-02
 */
public class LastRunInfoStore {
    private static final String KEY_VALUE_DELIMITER = "=";
    private static final String KEY_CONSECUTIVE_LAUNCH_CRASHES = "consecutiveLaunchCrashes";
    private static final String KEY_CRASHED = "crashed";
    private static final String KEY_CRASHED_DURING_LAUNCH = "crashedDuringLaunch";

    File file;
    private Logger logger;
    private ReentrantReadWriteLock lock;

    /**
     * constructor
     *
     * @param config
     */
    public LastRunInfoStore(ImmutableConfig config) {
        file = new File(config.getPersistenceDirectory(), "last-run-info");
        logger = config.getLogger();
        lock = new ReentrantReadWriteLock();
    }

    public File getFile() {
        return file;
    }

    public void setFile(File file) {
        this.file = file;
    }

    /**
     * persist
     *
     * @param lastRunInfo
     */
    public void persist(LastRunInfo lastRunInfo) {
        ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
        writeLock.lock();
        try {
            persistImpl(lastRunInfo);
        } finally {
            writeLock.unlock();
        }
    }

    private void persistImpl(LastRunInfo lastRunInfo) {
        KeyValueWriter keyValueWriter = new KeyValueWriter();
        keyValueWriter.add(KEY_CONSECUTIVE_LAUNCH_CRASHES, lastRunInfo.consecutiveLaunchCrashes);
        keyValueWriter.add(KEY_CRASHED, lastRunInfo.crashed);
        keyValueWriter.add(KEY_CRASHED_DURING_LAUNCH, lastRunInfo.crashedDuringLaunch);
        String text = keyValueWriter.toString();
        FileUtil.inputFile(file, text);
        logger.d("Persisted: $text");
    }

    /**
     * load last run info
     *
     * @return LastRunInfo
     */
    public LastRunInfo load() {
        ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
        readLock.lock();
        LastRunInfo lastRunInfo = loadImpl();
        readLock.unlock();
        return lastRunInfo;
    }

    private LastRunInfo loadImpl() {
        if (!file.exists()) {
            return null;
        }
        try {
            FileInputStream inputStream = new FileInputStream(file);
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream, Charset.forName("UTF-8"));
            StringWriter buffer = new StringWriter();
            FileUtils.copyTo(inputStreamReader, buffer);
            String[] lines = buffer.toString().split("\n");

            if (lines.length != 3) {
                logger.w("Unexpected number of lines when loading LastRunInfo. Skipping load. $lines");
                return null;
            } else {
                int consecutiveLaunchCrashes = asIntValue(lines[0], KEY_CONSECUTIVE_LAUNCH_CRASHES);
                boolean crashed = asBooleanValue(lines[1], KEY_CRASHED);
                boolean crashedDuringLaunch = asBooleanValue(lines[2], KEY_CRASHED_DURING_LAUNCH);
                LastRunInfo runInfo = new LastRunInfo(consecutiveLaunchCrashes, crashed, crashedDuringLaunch);
                logger.d("Loaded: $runInfo");
                return runInfo;
            }
        } catch (FileNotFoundException e) {
            e.getMessage();
            return null;
        } catch (NumberFormatException exc) {
            logger.w("Failed to read consecutiveLaunchCrashes from saved lastRunInfo", exc);
            return null;
        }
    }

    private int asIntValue(String missingDelimiterValue, String key) {
        return Integer.valueOf(substringAfter(key + KEY_VALUE_DELIMITER, missingDelimiterValue));
    }

    private boolean asBooleanValue(String missingDelimiterValue, String key) {
        return Boolean.valueOf(substringAfter(key + KEY_VALUE_DELIMITER, missingDelimiterValue));
    }

    private String substringAfter(String delimiter, String missingDelimiterValue) {
        int index = missingDelimiterValue.indexOf(delimiter);
        if (index == -1) {
            return missingDelimiterValue;
        } else {
            return missingDelimiterValue.substring(index + delimiter.length());
        }
    }
}
